This code is written by Christian Mosbæk Johannessen. It operationalizes Theo van Leeuwen’s “distinctive feature”-approach to typography, as well as elements of Andreas Stötzner’s “Signography”.

Table of Contents
1. Setting up
2. Deriving distinctive features from the two datasets
3. Summary Statistics
4. Principal Component Analysis

1. Setting up

1.1 Load packages
This chunk loads the packages required to execute the notebook.

1.2. Load stroke data
In order to run this notebook, two different data frames need to be loaded. The first contains the measures we made of the overall proportions of the fonts as well as individual strokes. The other contains counts of shape occurences.

The first data frame consists of 30 different measures made on each of 147 fonts. Figure 1 illustrates where in each sample the measures were made:

Fig. 1

Notice in the data frame that one observation (row) corresponds with one of a given letter’s strokes, 13 observations for each of the 147 fonts. The individual strokes are groped by Typeface (for example “Absolute Beauty”) and identified by Stroke (H1, H2, H3 etc.). This dataframe contains the values that the case asks you to attempt to automatically reproduce.

df <- read.csv("data/typography_data.csv", header = T, sep = ";")
df <- as.data.frame(df)

1.3. Load shape data
The second dataset to be included is not one I expect you to deal with as part of this case. It contains counts of bounded shapes (shape envelopes) as well as occurrences of three types of shape features, Straights, Angles and Curves, as well as the sum of these occurrences (Density). Figure 2 is an illustration of the principle behind the annotation of shape features.

Fig. 2
shpdf <- read.csv(file = "data/typography_shape_data.csv", header = T, sep = ";")
shpdf <- as.data.frame(shpdf)
attach(shpdf)
shpdf <- shpdf[order(Typeface, na.last = F),]

2. Deriving distinctive features from the two datasets

The next section of the script expands the dataframe df by deriving the variables Weight, Tension, Expansion, and Orientation from Van Leeuwen and Stötzner.

2.1. Weight
Deriving the average weight of all the strokes in a given font is a bit roundabout. First, all direct measures of stroke widths (for example 70 px and 113 px for stroke o1 in the figure) are converted to WSR (Weight Scale Rating) by relating them to the font’s X-height (384 px). This is done to establish a comparable reference of scale (lest image size and resolution becomes a factor in calculations). WSR expresses stroke width as a float between 0 and 1 (with one being a stroke of equal width to X-Height). Because many strokes, for example o1, are of uneven width, we then average over the narrowest and widest part of the stroke to arrive at a mean WSR.

Fig. 3

df$WSRnarrow <- df$narrow / df$Xheight

This calculates the Weight Scale Rating (WSR) of the narrowest measure of the stroke.

df$WSRwide <- df$wide / df$Xheight

This calculates the Weight Scale Rating (WSR) of the widest measure of the stroke.

df$Weight <- (df$WSRnarrow + df$WSRwide) / 2

This calculates the average WSR for the stroke.

2.2 Tension
The tension ratio of a stroke expresses the so-called “entasis” or gestural dynamics (for example the result of a change in force during the production of the stroke in hand writing) of the individual stroke by relating the narrowest and widest measurement of the stroke. In the script this is based on the original pixel measurements.

Fig. 4

df$Tension <- df$wide / df$narrow

2.3. Expansion
The expansion of a font expresses how broadly the letters sit on the baseline. It is derived by relating the width of the letter “o” to the x-height (based on an assumption that the look and feel of a font tends to be fairly homogenous. If one letter is designed to be broad, all letters will be).

Fig. 5

df$Expansion <- (df$Owidth / df$Xheight)

2.4. Orientation
The orientation of a font expresses the tallness of letters, that is to say, how long are the ascenders and descenders in relation to the body of the letter (x-height). It is derived here by relating a measure of the Vertical Orientation (vertical distance between the top-most and bottom-most parts of the sample) to the x-height.

Fig. 6

df$VertOrientation <- (df$Vorientation / df$Xheight)

2.5. begin building summary data frame for PCA
Create new data frame called summary.typeface with grouped calculations of means of slope, weight, tension, expansion and orientation (from df) using stats::aggregate(). One of the variables, Slope, based on a simple measurement of the angle between the stem of “k” and the base line (see fig. 7), reguires no further processing and is included as is.

Fig. 7.

summary.typeface <- aggregate(df[ , c("Index", "Slope" , "Weight" , "Tension" , "Expansion" , "VertOrientation")], by=list(df$Type , df$Typeface), FUN=mean)
summary.typeface <- as.data.frame (summary.typeface)

2.6. Connectivity
The connectivity of a font expresses the extent to which letters are connected to one another. Script and Handwritten, which emulates italic writing, tend to have a high degree of connectedness. Sans Serif, which contains no information about hand writing gestures, tend not be connected at all.

Fig. 8

Expand summary.typeface with variable Connectivity which we’re fetching from the shape_data.csv file (unelegant, but was an afterthought).

summary.typeface$Connectivity <- shpdf$Connectivity / 5
features <- c("Type", "Typeface", "Index", "Slope", "Weight", "Tension", "Expansion", "VertOrientation", "Connectivity")
names(summary.typeface) <- features
rm(features) # A bit of housekeeping

2.7. Contrast
Contrast expresses the difference in weight of the thinnest and thickest stroke in a letter.

Fig. 9

Because letters have a different number of strokes (“H” has three, “o” has one), automatically calculating Contrast between strokes is a bit more tricky than the previous features (which either took the stroke or the entire letter as their unit of analysis) and needs some further lines of code:

The next chunk simply calculates how many fonts are currently in the dataset (under the assumption that each font has 13 rows, one for each stroke):

observations <- nrow(df)/13

Because the way we identified strokes in the dataset (conflating letters and strokes in a single identifier, e.g. H1, H2) makes subsetting difficult, the next chunk adds a variable, Letter, and repeats a factor to build a new identifier (Turns out I could have done this more easily in OpenRefine).

Letter <- c(rep("H", 3), rep("p", 2), rep("k", 3), rep("x", 2), rep("o", 1), rep("a", 2)) # A bit of data wrangling adding a variable, letter, because the way we formated 'stroke' in the dataset makes subsetting difficult
df$Letter <- rep(Letter, observations)
rm(Letter, observations) # Housekeeping

The next chunk adds a script to identify strokes with minimum and maximum weight for each letter and calculate a contrast ratio (min / max):

contrast.ratio <- function(L){
  chosen <- subset(df, df$Letter==L)
  a <- sapply(split(chosen$Weight, chosen$Typeface), min)
  b <- sapply(split(chosen$Weight, chosen$Typeface), max)
  c <- b / a
  return(c)
}

The next chunk creates a new intermediate dataframe, df.contrast, with columns for calculated differences for each letter (and adding the letter you want in place of ‘L’).

df.contrast <- data.frame(contrast.ratio("H"), contrast.ratio("p"), contrast.ratio("k"), contrast.ratio("x"), contrast.ratio("o"), contrast.ratio("a"))

Column names in df.contrast are hobbile, so rename them with friendlier letters:

column.headings <- c("H", "p", "k", "x", "o", "a")
names(df.contrast) <- column.headings
rm(column.headings, contrast.ratio)

Add a column, mean to df.contrast and fill with calculated means for each typeface:

df.contrast$mean <- rowSums(df.contrast) / ncol(df.contrast)

Fetch mean for each typeface from df.contrast and add as column Contrast to summary.typeface:

summary.typeface$Contrast <- df.contrast$mean
rm(df.contrast) # housekeeping

2.8. Shape
The next lines derive shape descriptors; (1) Density per region (Dr), (2) Straight as proportion of Density (SD), (3) Angle as proportion of Density (AD) and (4) Curve as proportion of Density (CD), and adds them to summary.

summary.typeface$Dr <- (shpdf$Straight + shpdf$Angle + shpdf$Curve) / shpdf$Regions

This adds Dr to the summary.

summary.typeface$SD <- shpdf$Straight / (shpdf$Straight + shpdf$Angle + shpdf$Curve)

This adds SD to the summary.

summary.typeface$AD <- shpdf$Angle / (shpdf$Straight + shpdf$Angle + shpdf$Curve)

This adds AD to the summary.

summary.typeface$CD <- shpdf$Curve / (shpdf$Straight + shpdf$Angle + shpdf$Curve)

This adds CD to the summary.

3. Summary Statistics

Before moving on to Principal Compåonent Analysis, here are statistical summaries for each of the five typographical classes, Sans, Serif, Slab Serif, Handwritten and Script.

3.1. Sans Serif Typefaces

summary.sans <- subset(summary.typeface, summary.typeface$Type == "Sans")
summary(summary.sans)
     Type             Typeface             Index       Slope        Weight          Tension        Expansion     
 Length:24          Length:24          Min.   :1   Min.   :90   Min.   :0.1019   Min.   :1.025   Min.   :0.6113  
 Class :character   Class :character   1st Qu.:1   1st Qu.:90   1st Qu.:0.1394   1st Qu.:1.060   1st Qu.:0.8569  
 Mode  :character   Mode  :character   Median :1   Median :90   Median :0.1575   Median :1.084   Median :1.0118  
                                       Mean   :1   Mean   :90   Mean   :0.1570   Mean   :1.092   Mean   :0.9498  
                                       3rd Qu.:1   3rd Qu.:90   3rd Qu.:0.1665   3rd Qu.:1.114   3rd Qu.:1.0524  
                                       Max.   :1   Max.   :90   Max.   :0.2262   Max.   :1.173   Max.   :1.0667  
 VertOrientation  Connectivity    Contrast           Dr               SD               AD               CD         
 Min.   :1.002   Min.   :0     Min.   :1.030   Min.   : 8.614   Min.   :0.2215   Min.   :0.1049   Min.   :0.09091  
 1st Qu.:1.769   1st Qu.:0     1st Qu.:1.053   1st Qu.:11.750   1st Qu.:0.4082   1st Qu.:0.4228   1st Qu.:0.11268  
 Median :1.822   Median :0     Median :1.064   Median :12.222   Median :0.4286   Median :0.4455   Median :0.12406  
 Mean   :1.826   Mean   :0     Mean   :1.071   Mean   :12.607   Mean   :0.4064   Mean   :0.3968   Mean   :0.19678  
 3rd Qu.:1.896   3rd Qu.:0     3rd Qu.:1.090   3rd Qu.:12.583   3rd Qu.:0.4343   3rd Qu.:0.4537   3rd Qu.:0.16552  
 Max.   :2.269   Max.   :0     Max.   :1.144   Max.   :18.000   Max.   :0.4636   Max.   :0.4595   Max.   :0.62346  

3.2. Serif (Humanist) Typefaces

summary.serif <- subset(summary.typeface, summary.typeface$Type == "Serif")
summary(summary.serif)
     Type             Typeface             Index       Slope        Weight           Tension        Expansion     
 Length:33          Length:33          Min.   :2   Min.   :90   Min.   :0.05591   Min.   :1.379   Min.   :0.7011  
 Class :character   Class :character   1st Qu.:2   1st Qu.:90   1st Qu.:0.13295   1st Qu.:1.690   1st Qu.:0.9737  
 Mode  :character   Mode  :character   Median :2   Median :90   Median :0.14700   Median :1.919   Median :1.0329  
                                       Mean   :2   Mean   :90   Mean   :0.14908   Mean   :2.251   Mean   :1.0056  
                                       3rd Qu.:2   3rd Qu.:90   3rd Qu.:0.17009   3rd Qu.:2.529   3rd Qu.:1.0682  
                                       Max.   :2   Max.   :90   Max.   :0.23401   Max.   :7.575   Max.   :1.2185  
 VertOrientation  Connectivity        Contrast           Dr              SD               AD         
 Min.   :1.000   Min.   :0.00000   Min.   :1.312   Min.   :17.00   Min.   :0.1326   Min.   :0.06542  
 1st Qu.:1.954   1st Qu.:0.00000   1st Qu.:1.681   1st Qu.:23.90   1st Qu.:0.2455   1st Qu.:0.25379  
 Median :2.030   Median :0.00000   Median :1.921   Median :25.56   Median :0.2806   Median :0.29644  
 Mean   :2.041   Mean   :0.04242   Mean   :2.227   Mean   :25.68   Mean   :0.2973   Mean   :0.29918  
 3rd Qu.:2.133   3rd Qu.:0.00000   3rd Qu.:2.625   3rd Qu.:28.11   3rd Qu.:0.3502   3rd Qu.:0.36187  
 Max.   :2.603   Max.   :0.20000   Max.   :5.187   Max.   :31.25   Max.   :0.4522   Max.   :0.46324  
       CD         
 Min.   :0.08456  
 1st Qu.:0.27426  
 Median :0.42292  
 Mean   :0.40348  
 3rd Qu.:0.52096  
 Max.   :0.70870  

3.3. Slab Serif Typefaces

summary.slab <- subset(summary.typeface, summary.typeface$Type == "Slab")
summary(summary.slab)
     Type             Typeface             Index       Slope        Weight           Tension        Expansion     
 Length:33          Length:33          Min.   :3   Min.   :90   Min.   :0.08438   Min.   :1.008   Min.   :0.5413  
 Class :character   Class :character   1st Qu.:3   1st Qu.:90   1st Qu.:0.15080   1st Qu.:1.109   1st Qu.:0.9626  
 Mode  :character   Mode  :character   Median :3   Median :90   Median :0.17886   Median :1.173   Median :1.0343  
                                       Mean   :3   Mean   :90   Mean   :0.19402   Mean   :1.236   Mean   :1.0222  
                                       3rd Qu.:3   3rd Qu.:90   3rd Qu.:0.22524   3rd Qu.:1.280   3rd Qu.:1.0816  
                                       Max.   :3   Max.   :90   Max.   :0.35503   Max.   :2.368   Max.   :1.6024  
 VertOrientation  Connectivity    Contrast           Dr               SD               AD                CD         
 Min.   :1.000   Min.   :0     Min.   :1.011   Min.   : 7.125   Min.   :0.1239   Min.   :0.04317   Min.   :0.00000  
 1st Qu.:1.769   1st Qu.:0     1st Qu.:1.102   1st Qu.:23.111   1st Qu.:0.3626   1st Qu.:0.29388   1st Qu.:0.08108  
 Median :1.870   Median :0     Median :1.149   Median :24.333   Median :0.4384   Median :0.45455   Median :0.11330  
 Mean   :1.834   Mean   :0     Mean   :1.189   Mean   :23.985   Mean   :0.3982   Mean   :0.37655   Mean   :0.22526  
 3rd Qu.:1.914   3rd Qu.:0     3rd Qu.:1.223   3rd Qu.:25.111   3rd Qu.:0.4554   3rd Qu.:0.47032   3rd Qu.:0.33333  
 Max.   :2.188   Max.   :0     Max.   :1.703   Max.   :34.800   Max.   :0.5000   Max.   :0.50000   Max.   :0.80769  

3.4. Handwritten Typefaces

summary.hand <- subset(summary.typeface, summary.typeface$Type == "Hand")
summary(summary.hand)
     Type             Typeface             Index       Slope           Weight           Tension        Expansion     
 Length:22          Length:22          Min.   :4   Min.   : 95.0   Min.   :0.05377   Min.   :1.067   Min.   :0.1957  
 Class :character   Class :character   1st Qu.:4   1st Qu.:102.2   1st Qu.:0.10357   1st Qu.:1.424   1st Qu.:0.4476  
 Mode  :character   Mode  :character   Median :4   Median :108.0   Median :0.12756   Median :1.944   Median :0.6044  
                                       Mean   :4   Mean   :109.0   Mean   :0.13221   Mean   :2.149   Mean   :0.5697  
                                       3rd Qu.:4   3rd Qu.:114.0   3rd Qu.:0.15940   3rd Qu.:2.425   3rd Qu.:0.6492  
                                       Max.   :4   Max.   :126.0   Max.   :0.28110   Max.   :4.763   Max.   :1.0354  
 VertOrientation  Connectivity       Contrast           Dr               SD                AD         
 Min.   :1.517   Min.   :0.0000   Min.   :1.039   Min.   : 6.939   Min.   :0.04482   Min.   :0.03306  
 1st Qu.:1.860   1st Qu.:0.2500   1st Qu.:1.133   1st Qu.:12.583   1st Qu.:0.09568   1st Qu.:0.12206  
 Median :2.249   Median :0.6000   Median :1.232   Median :15.972   Median :0.14535   Median :0.15774  
 Mean   :2.317   Mean   :0.5455   Mean   :1.250   Mean   :19.249   Mean   :0.14993   Mean   :0.17000  
 3rd Qu.:2.562   3rd Qu.:0.8000   3rd Qu.:1.351   3rd Qu.:24.542   3rd Qu.:0.18738   3rd Qu.:0.22052  
 Max.   :4.198   Max.   :1.0000   Max.   :1.635   Max.   :42.667   Max.   :0.29630   Max.   :0.43243  
       CD        
 Min.   :0.2838  
 1st Qu.:0.5684  
 Median :0.7014  
 Mean   :0.6801  
 3rd Qu.:0.7559  
 Max.   :0.9186  

3.5. Script Typefaces

summary.script <- subset(summary.typeface, summary.typeface$Type == "Script")
summary(summary.script)
     Type             Typeface             Index       Slope           Weight           Tension      
 Length:35          Length:35          Min.   :5   Min.   : 86.0   Min.   :0.07272   Min.   : 1.107  
 Class :character   Class :character   1st Qu.:5   1st Qu.: 99.5   1st Qu.:0.09179   1st Qu.: 1.749  
 Mode  :character   Mode  :character   Median :5   Median :108.0   Median :0.13372   Median : 2.148  
                                       Mean   :5   Mean   :106.4   Mean   :0.13423   Mean   : 2.587  
                                       3rd Qu.:5   3rd Qu.:113.5   3rd Qu.:0.17170   3rd Qu.: 2.689  
                                       Max.   :5   Max.   :121.0   Max.   :0.24585   Max.   :14.408  
   Expansion      VertOrientation   Connectivity       Contrast           Dr               SD          
 Min.   :0.2411   Min.   :0.9578   Min.   :0.0000   Min.   :1.056   Min.   : 4.702   Min.   :0.004246  
 1st Qu.:0.4367   1st Qu.:1.8998   1st Qu.:0.4000   1st Qu.:1.230   1st Qu.:10.452   1st Qu.:0.050377  
 Median :0.5571   Median :2.2313   Median :0.6000   Median :1.312   Median :18.286   Median :0.081081  
 Mean   :0.5740   Mean   :2.4403   Mean   :0.6057   Mean   :1.345   Mean   :19.347   Mean   :0.085487  
 3rd Qu.:0.6995   3rd Qu.:2.3885   3rd Qu.:0.8000   3rd Qu.:1.464   3rd Qu.:24.643   3rd Qu.:0.108854  
 Max.   :0.9552   Max.   :6.1803   Max.   :1.0000   Max.   :1.676   Max.   :67.286   Max.   :0.231884  
       AD                 CD        
 Min.   :0.007363   Min.   :0.5547  
 1st Qu.:0.042781   1st Qu.:0.6912  
 Median :0.122699   Median :0.7625  
 Mean   :0.128530   Mean   :0.7860  
 3rd Qu.:0.192302   3rd Qu.:0.9007  
 Max.   :0.335938   Max.   :0.9737  

5. Principal Component Analysis

Using prcomp() with normalization (correlation matrix) by setting scale = TRUE (FALSE gives us a covariance matrix in stead):

res.pca <- prcomp(summary.typeface[ , c(4:14)], scale = T)

The next chunk outputs a scree plot to examine how many PCs we need to consider and what is noise:

get_eigenvalue(res.pca)
fviz_eig(res.pca) # Seems the first 3 are pretty relevant (74.7 % variance explained). Adding the 4th brings us to 88% variance explained.

Examine the results for Variables of the PCA

res.var <- get_pca_var(res.pca)
res.var$coord          # Coordinates
                      Dim.1        Dim.2      Dim.3       Dim.4       Dim.5       Dim.6        Dim.7        Dim.8
Slope           -0.77615564 -0.097714126  0.2462106  0.21386565 -0.01366866 -0.30053487  0.146898971  0.388050561
Weight           0.43093206  0.051914556  0.1765697  0.82800935  0.17181240  0.17318869 -0.134649524 -0.076759572
Tension         -0.37714642  0.651001270 -0.2688038  0.22666748  0.16911367 -0.44471350  0.217409570 -0.166855755
Expansion        0.81162249  0.240471242  0.1240455  0.11395402  0.07780639  0.25017459  0.189057604  0.238184867
VertOrientation -0.42440175  0.008505285  0.5214649 -0.26168552  0.68241263  0.07964058  0.034618125 -0.077604057
Connectivity    -0.81668178 -0.054272729  0.2592759  0.10387191 -0.10118690 -0.18514481 -0.370878656  0.013184892
Contrast         0.01657250  0.876624489 -0.2002308 -0.18316018  0.10914291  0.13335294 -0.282694783  0.187672307
Dr               0.04050132  0.522040022  0.6912037 -0.07593518 -0.45804150  0.04886883  0.101919593 -0.128116694
SD               0.94654120 -0.051791245  0.1013813 -0.10744219  0.05555730 -0.15698183 -0.008095973  0.036138915
AD               0.86594030 -0.037206074  0.1305609 -0.04776382  0.04465815 -0.42586082 -0.129692673  0.004703778
CD              -0.93626467  0.045933977 -0.1199579  0.07998068 -0.05174420  0.30215606  0.071644680 -0.020989410
                       Dim.9       Dim.10        Dim.11
Slope           -0.137770434  0.004991307 -1.220273e-31
Weight          -0.106154487  0.004581275  3.816395e-32
Tension          0.086669692 -0.036543027 -9.466018e-33
Expansion        0.306514672  0.001578536  8.644617e-32
VertOrientation -0.008751778  0.008880375  2.279021e-32
Connectivity     0.253458793 -0.074755645 -3.744679e-32
Contrast        -0.112575839  0.010445805 -1.068454e-32
Dr              -0.056794372  0.009376637 -2.962442e-33
SD              -0.085307164 -0.207789908  1.422958e-16
AD               0.038353904  0.166644640  1.443278e-16
CD               0.023809071  0.019889292  2.773448e-16
res.var$contrib # Contributions to the PCs
                       Dim.1        Dim.2      Dim.3      Dim.4       Dim.5      Dim.6       Dim.7        Dim.8
Slope           12.144350458  0.618371833  5.6225216  4.8944045  0.02427157 12.2259837  5.77308627 50.086250006
Weight           3.743641691  0.174547362  2.8916766 73.3648880  3.83490652  4.0600665  4.85042942  1.959778429
Tension          2.867455129 27.447239431  6.7017508  5.4978922  3.71537960 26.7703890 12.64526275  9.260283089
Expansion       13.279594169  3.745081663  1.4271835  1.3895594  0.78645951  8.4718931  9.56222242 18.869926755
VertOrientation  3.631039584  0.004685034 25.2213156  7.3278603 60.49784375  0.8585446  0.32061041  2.003137403
Connectivity    13.445668135  0.190764837  6.2350788  1.1545534  1.33013096  4.6399911 36.79884802  0.057822256
Contrast         0.005536721 49.769376060  3.7185974  3.5898799  1.54752208  2.4071283 21.37992272 11.714999347
Dr               0.033068538 17.649914130 44.3128484  0.6170265 27.25558135  0.3232645  2.77898310  5.459505805
SD              18.061578655  0.173719149  0.9533076  1.2352860  0.40098490  3.3357449  0.01753511  0.434402289
AD              15.116545826  0.089652557  1.5810448  0.2441270  0.25908801 24.5487526  4.49988509  0.007359284
CD              17.671521093  0.136647945  1.3346749  0.6845229  0.34783174 12.3582417  1.37321469  0.146535337
                      Dim.9       Dim.10       Dim.11
Slope            8.57906015  0.031699838 1.261933e-29
Weight           5.09335997  0.026705532 1.234324e-30
Tension          3.39517500  1.699172951 7.593766e-32
Expansion       42.46490889  0.003170573 6.333068e-30
VertOrientation  0.03461945  0.100343948 4.401688e-31
Connectivity    29.03637420  7.110768282 1.188370e-30
Contrast         5.72819827  0.138839286 9.674637e-32
Dr               1.45793536  0.111872352 7.437421e-33
SD               3.28926354 54.938581032 1.715960e+01
AD               0.66488543 35.335499575 1.765316e+01
CD               0.25621973  0.503346630 6.518724e+01
correlation_matrix <- as.data.frame(res.var$coord) # Store the correlations as a matrix we can examine and use in the article
PCs <- c("PC1", "PC2", "PC3", "PC4", "PC5", "PC6", "PC7", "PC8", "PC9", "PC10", "PC11") # Rename the variables from Dim.1 to PC1, which is more transparent
colnames(correlation_matrix) <- PCs
rm(PCs) # Housekeeping

Perform Kaiser-Meyer-Olkin test for measuring sampling adequacy. Result is miserable we’re only getting a MSA = 0.5

KMO(correlation_matrix)
Error in solve.default(r) : 
  system is computationally singular: reciprocal condition number = 5.52332e-18
matrix is not invertible, image not found
Kaiser-Meyer-Olkin factor adequacy
Call: KMO(r = correlation_matrix)
Overall MSA =  0.5
MSA for each item = 
 PC1  PC2  PC3  PC4  PC5  PC6  PC7  PC8  PC9 PC10 PC11 
 0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5 

Make fancy biplot of PC1 vs. PC2

type <- as.factor(summary.typeface$Type) # Subsets types for coloring the plot
fviz_pca_biplot(res.pca,
                axes = c(1, 2),
                col.ind = type,
                geom = c("point", "text"),
                label = "all", labelsize = 3, invisible = "none",
                repel = FALSE,
                habillage = "none", 
                palette = NULL,
                addEllipses = TRUE, title = "PCA - Biplot"
)

rm(type) # Housekeeping
type <- as.factor(summary.typeface$Type) # Subsets types for coloring the plot
fviz_pca_biplot(res.pca,
                axes = c(1, 3),
                col.ind = type,
                geom = c("point", "text"),
                label = "all", labelsize = 3, invisible = "none",
                repel = FALSE,
                habillage = "none", 
                palette = NULL,
                addEllipses = TRUE, title = "PCA - Biplot"
)

rm(type) # Housekeeping

Make less fancy biplots, trying to rotate the data to reveal other structures (these are the two most interesting ones)

ggbiplot(res.pca, choices = c(1,2), groups = summary.typeface$Type, ellipse = T, ellipse.prob = .95)

ggbiplot(res.pca, choices = c(1,3), groups = summary.typeface$Type, ellipse = T, ellipse.prob = .95)

LS0tCnRpdGxlOiAiQSBRdWFudGl0YXRpdmUgYXBwcm9hY2ggdG8gRGlzdGluY3RpdmUgRmVhdHVyZXMgb2YgVHlwb2dyYXBoeSIKQXV0aG9yOiAiQ2hyaXN0aWFuIE1vc2LDpmsgSm9oYW5ubmVzc2VuLCBVbml2ZXJzaXR5IG9mIFNvdXRoZXJuIERlbm1hcmssIE9kZW5zZSwgRGVubWFyayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQogIApUaGlzIGNvZGUgaXMgd3JpdHRlbiBieSBDaHJpc3RpYW4gTW9zYsOmayBKb2hhbm5lc3Nlbi4gSXQgb3BlcmF0aW9uYWxpemVzIFRoZW8gdmFuIExlZXV3ZW4ncyAiZGlzdGluY3RpdmUgZmVhdHVyZSItYXBwcm9hY2ggdG8gdHlwb2dyYXBoeSwgYXMgd2VsbCBhcyBlbGVtZW50cyBvZiBBbmRyZWFzIFN0w7Z0em5lcidzICJTaWdub2dyYXBoeSIuCgoqKlRhYmxlIG9mIENvbnRlbnRzKipcCjEuIFNldHRpbmcgdXBcCjIuIERlcml2aW5nIGRpc3RpbmN0aXZlIGZlYXR1cmVzIGZyb20gdGhlIHR3byBkYXRhc2V0c1wKMy4gU3VtbWFyeSBTdGF0aXN0aWNzXAo0LiBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzXAoKIyAxLiBTZXR0aW5nIHVwXAoqKjEuMSBMb2FkIHBhY2thZ2VzKipcClRoaXMgY2h1bmsgbG9hZHMgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIHRvIGV4ZWN1dGUgdGhlIG5vdGVib29rLgoKYGBge3IgaW5jbHVkZT1GQUxTRX0KcGFja2FnZXMgPC0gYygiZ2dwbG90MiIsICJnZ2JpcGxvdCIsICJIbWlzYyIsICJmYWN0b2V4dHJhIiwgIkZhY3RvQ2xhc3MiLCAibGF0dGljZSIsICJwbG90bHkiLCAicmdsIiwgImNhciIsImhlcmUiLCAiZWZmc2l6ZSIsICJwc3ljaCIpCmxhcHBseShwYWNrYWdlcywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQpybShwYWNrYWdlcykKYGBgCgoqKjEuMi4gTG9hZCBzdHJva2UgZGF0YSoqXApJbiBvcmRlciB0byBydW4gdGhpcyBub3RlYm9vaywgdHdvIGRpZmZlcmVudCBkYXRhIGZyYW1lcyBuZWVkIHRvIGJlIGxvYWRlZC4gVGhlIGZpcnN0IGNvbnRhaW5zIHRoZSBtZWFzdXJlcyB3ZSBtYWRlIG9mIHRoZSBvdmVyYWxsIHByb3BvcnRpb25zIG9mIHRoZSBmb250cyBhcyB3ZWxsIGFzIGluZGl2aWR1YWwgc3Ryb2tlcy4gVGhlIG90aGVyIGNvbnRhaW5zIGNvdW50cyBvZiBzaGFwZSBvY2N1cmVuY2VzLgoKVGhlIGZpcnN0IGRhdGEgZnJhbWUgY29uc2lzdHMgb2YgMzAgZGlmZmVyZW50IG1lYXN1cmVzIG1hZGUgb24gZWFjaCBvZiAxNDcgZm9udHMuIEZpZ3VyZSAxIGlsbHVzdHJhdGVzIHdoZXJlIGluIGVhY2ggc2FtcGxlIHRoZSBtZWFzdXJlcyB3ZXJlIG1hZGU6XAoKIVtGaWcuIDFdKGdmeC8xLnBuZykKCk5vdGljZSBpbiB0aGUgZGF0YSBmcmFtZSB0aGF0IG9uZSBvYnNlcnZhdGlvbiAocm93KSBjb3JyZXNwb25kcyB3aXRoIG9uZSBvZiBhIGdpdmVuIGxldHRlcidzIHN0cm9rZXMsIDEzIG9ic2VydmF0aW9ucyBmb3IgZWFjaCBvZiB0aGUgMTQ3IGZvbnRzLiBUaGUgaW5kaXZpZHVhbCBzdHJva2VzIGFyZSBncm9wZWQgYnkgYGBgVHlwZWZhY2VgYGAgKGZvciBleGFtcGxlICJBYnNvbHV0ZSBCZWF1dHkiKSBhbmQgaWRlbnRpZmllZCBieSBgYGBTdHJva2VgYGAgKEgxLCBIMiwgSDMgZXRjLikuIFRoaXMgZGF0YWZyYW1lIGNvbnRhaW5zIHRoZSB2YWx1ZXMgdGhhdCB0aGUgY2FzZSBhc2tzIHlvdSB0byBhdHRlbXB0IHRvIGF1dG9tYXRpY2FsbHkgcmVwcm9kdWNlLgoKYGBge3J9CmRmIDwtIHJlYWQuY3N2KCJkYXRhL3R5cG9ncmFwaHlfZGF0YS5jc3YiLCBoZWFkZXIgPSBULCBzZXAgPSAiOyIpCmRmIDwtIGFzLmRhdGEuZnJhbWUoZGYpCmBgYAoKKioxLjMuIExvYWQgc2hhcGUgZGF0YSoqXApUaGUgc2Vjb25kIGRhdGFzZXQgdG8gYmUgaW5jbHVkZWQgaXMgbm90IG9uZSBJIGV4cGVjdCB5b3UgdG8gZGVhbCB3aXRoIGFzIHBhcnQgb2YgdGhpcyBjYXNlLiBJdCBjb250YWlucyBjb3VudHMgb2YgYm91bmRlZCBzaGFwZXMgKHNoYXBlIGVudmVsb3BlcykgYXMgd2VsbCBhcyBvY2N1cnJlbmNlcyBvZiB0aHJlZSB0eXBlcyBvZiBzaGFwZSBmZWF0dXJlcywgU3RyYWlnaHRzLCBBbmdsZXMgYW5kIEN1cnZlcywgYXMgd2VsbCBhcyB0aGUgc3VtIG9mIHRoZXNlIG9jY3VycmVuY2VzIChEZW5zaXR5KS4gRmlndXJlIDIgaXMgYW4gaWxsdXN0cmF0aW9uIG9mIHRoZSBwcmluY2lwbGUgYmVoaW5kIHRoZSBhbm5vdGF0aW9uIG9mIHNoYXBlIGZlYXR1cmVzLlwKCjxjZW50ZXI+CgohW0ZpZy4gMl0oZ2Z4LzIucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKYGBge3J9CnNocGRmIDwtIHJlYWQuY3N2KGZpbGUgPSAiZGF0YS90eXBvZ3JhcGh5X3NoYXBlX2RhdGEuY3N2IiwgaGVhZGVyID0gVCwgc2VwID0gIjsiKQpzaHBkZiA8LSBhcy5kYXRhLmZyYW1lKHNocGRmKQphdHRhY2goc2hwZGYpCnNocGRmIDwtIHNocGRmW29yZGVyKFR5cGVmYWNlLCBuYS5sYXN0ID0gRiksXQpgYGAKCiMgMi4gRGVyaXZpbmcgZGlzdGluY3RpdmUgZmVhdHVyZXMgZnJvbSB0aGUgdHdvIGRhdGFzZXRzXApUaGUgbmV4dCBzZWN0aW9uIG9mIHRoZSBzY3JpcHQgZXhwYW5kcyB0aGUgZGF0YWZyYW1lIGBgYGRmYGBgIGJ5IGRlcml2aW5nIHRoZSB2YXJpYWJsZXMgV2VpZ2h0LCBUZW5zaW9uLCBFeHBhbnNpb24sIGFuZCBPcmllbnRhdGlvbiBmcm9tIFZhbiBMZWV1d2VuIGFuZCBTdMO2dHpuZXIuCgoqKjIuMS4gV2VpZ2h0KipcCkRlcml2aW5nIHRoZSBhdmVyYWdlIHdlaWdodCBvZiBhbGwgdGhlIHN0cm9rZXMgaW4gYSBnaXZlbiBmb250IGlzIGEgYml0IHJvdW5kYWJvdXQuIEZpcnN0LCBhbGwgZGlyZWN0IG1lYXN1cmVzIG9mIHN0cm9rZSB3aWR0aHMgKGZvciBleGFtcGxlIDcwIHB4IGFuZCAxMTMgcHggZm9yIHN0cm9rZSBvMSBpbiB0aGUgZmlndXJlKSBhcmUgY29udmVydGVkIHRvIFdTUiAoV2VpZ2h0IFNjYWxlIFJhdGluZykgYnkgcmVsYXRpbmcgdGhlbSB0byB0aGUgZm9udCdzIFgtaGVpZ2h0ICgzODQgcHgpLiBUaGlzIGlzIGRvbmUgdG8gZXN0YWJsaXNoIGEgY29tcGFyYWJsZSByZWZlcmVuY2Ugb2Ygc2NhbGUgKGxlc3QgaW1hZ2Ugc2l6ZSBhbmQgcmVzb2x1dGlvbiBiZWNvbWVzIGEgZmFjdG9yIGluIGNhbGN1bGF0aW9ucykuIFdTUiAgZXhwcmVzc2VzIHN0cm9rZSB3aWR0aCBhcyBhIGZsb2F0IGJldHdlZW4gMCBhbmQgMSAod2l0aCBvbmUgYmVpbmcgYSBzdHJva2Ugb2YgZXF1YWwgd2lkdGggdG8gWC1IZWlnaHQpLiBCZWNhdXNlIG1hbnkgc3Ryb2tlcywgZm9yIGV4YW1wbGUgbzEsIGFyZSBvZiB1bmV2ZW4gd2lkdGgsIHdlIHRoZW4gYXZlcmFnZSBvdmVyIHRoZSBuYXJyb3dlc3QgYW5kIHdpZGVzdCBwYXJ0IG9mIHRoZSBzdHJva2UgdG8gYXJyaXZlIGF0IGEgbWVhbiBXU1IuCgo8Y2VudGVyPgoKIVtGaWcuIDNdKGdmeC8zLnBuZyl7d2lkdGg9NTAlfQoKPC9jZW50ZXI+CgpgYGB7cn0KZGYkV1NSbmFycm93IDwtIGRmJG5hcnJvdyAvIGRmJFhoZWlnaHQKYGBgClRoaXMgY2FsY3VsYXRlcyB0aGUgV2VpZ2h0IFNjYWxlIFJhdGluZyAoV1NSKSBvZiB0aGUgbmFycm93ZXN0IG1lYXN1cmUgb2YgdGhlIHN0cm9rZS4KYGBge3J9CmRmJFdTUndpZGUgPC0gZGYkd2lkZSAvIGRmJFhoZWlnaHQKYGBgClRoaXMgY2FsY3VsYXRlcyB0aGUgV2VpZ2h0IFNjYWxlIFJhdGluZyAoV1NSKSBvZiB0aGUgd2lkZXN0IG1lYXN1cmUgb2YgdGhlIHN0cm9rZS4KYGBge3J9CmRmJFdlaWdodCA8LSAoZGYkV1NSbmFycm93ICsgZGYkV1NSd2lkZSkgLyAyCmBgYApUaGlzIGNhbGN1bGF0ZXMgdGhlIGF2ZXJhZ2UgV1NSIGZvciB0aGUgc3Ryb2tlLgoKKioyLjIgVGVuc2lvbioqXApUaGUgdGVuc2lvbiByYXRpbyBvZiBhIHN0cm9rZSBleHByZXNzZXMgdGhlIHNvLWNhbGxlZCAiZW50YXNpcyIgb3IgZ2VzdHVyYWwgZHluYW1pY3MgKGZvciBleGFtcGxlIHRoZSByZXN1bHQgb2YgYSBjaGFuZ2UgaW4gZm9yY2UgZHVyaW5nIHRoZSBwcm9kdWN0aW9uIG9mIHRoZSBzdHJva2UgaW4gaGFuZCB3cml0aW5nKSBvZiB0aGUgaW5kaXZpZHVhbCBzdHJva2UgYnkgcmVsYXRpbmcgdGhlIG5hcnJvd2VzdCBhbmQgd2lkZXN0IG1lYXN1cmVtZW50IG9mIHRoZSBzdHJva2UuIEluIHRoZSBzY3JpcHQgdGhpcyBpcyBiYXNlZCBvbiB0aGUgb3JpZ2luYWwgcGl4ZWwgbWVhc3VyZW1lbnRzLgoKPGNlbnRlcj4KCiFbRmlnLiA0XShnZngvNC5wbmcpe3dpZHRoPTUwJX0KCjwvY2VudGVyPgoKYGBge3J9CmRmJFRlbnNpb24gPC0gZGYkd2lkZSAvIGRmJG5hcnJvdwpgYGAKCgoqKjIuMy4gRXhwYW5zaW9uKipcClRoZSBleHBhbnNpb24gb2YgYSBmb250IGV4cHJlc3NlcyBob3cgYnJvYWRseSB0aGUgbGV0dGVycyBzaXQgb24gdGhlIGJhc2VsaW5lLiBJdCBpcyBkZXJpdmVkIGJ5IHJlbGF0aW5nIHRoZSB3aWR0aCBvZiB0aGUgbGV0dGVyICJvIiB0byB0aGUgeC1oZWlnaHQgKGJhc2VkIG9uIGFuIGFzc3VtcHRpb24gdGhhdCB0aGUgbG9vayBhbmQgZmVlbCBvZiBhIGZvbnQgdGVuZHMgdG8gYmUgZmFpcmx5IGhvbW9nZW5vdXMuIElmIG9uZSBsZXR0ZXIgaXMgZGVzaWduZWQgdG8gYmUgYnJvYWQsIGFsbCBsZXR0ZXJzIHdpbGwgYmUpLgoKPGNlbnRlcj4KCiFbRmlnLiA1XShnZngvNy5wbmcpe3dpZHRoPTUwJX0KCjwvY2VudGVyPgoKYGBge3J9CmRmJEV4cGFuc2lvbiA8LSAoZGYkT3dpZHRoIC8gZGYkWGhlaWdodCkKYGBgCgoKKioyLjQuIE9yaWVudGF0aW9uKipcClRoZSBvcmllbnRhdGlvbiBvZiBhIGZvbnQgZXhwcmVzc2VzIHRoZSB0YWxsbmVzcyBvZiBsZXR0ZXJzLCB0aGF0IGlzIHRvIHNheSwgaG93IGxvbmcgYXJlIHRoZSBhc2NlbmRlcnMgYW5kIGRlc2NlbmRlcnMgaW4gcmVsYXRpb24gdG8gdGhlIGJvZHkgb2YgdGhlIGxldHRlciAoeC1oZWlnaHQpLiBJdCBpcyBkZXJpdmVkIGhlcmUgYnkgcmVsYXRpbmcgYSBtZWFzdXJlIG9mIHRoZSBWZXJ0aWNhbCBPcmllbnRhdGlvbiAodmVydGljYWwgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdG9wLW1vc3QgYW5kIGJvdHRvbS1tb3N0IHBhcnRzIG9mIHRoZSBzYW1wbGUpIHRvIHRoZSB4LWhlaWdodC4KCjxjZW50ZXI+CgohW0ZpZy4gNl0oZ2Z4LzYucG5nKXt3aWR0aD01MCV9Cgo8L2NlbnRlcj4KCmBgYHtyfQpkZiRWZXJ0T3JpZW50YXRpb24gPC0gKGRmJFZvcmllbnRhdGlvbiAvIGRmJFhoZWlnaHQpCmBgYAoKKioyLjUuIGJlZ2luIGJ1aWxkaW5nIHN1bW1hcnkgZGF0YSBmcmFtZSBmb3IgUENBKipcCkNyZWF0ZSBuZXcgZGF0YSBmcmFtZSBjYWxsZWQgYGBgc3VtbWFyeS50eXBlZmFjZWBgYCB3aXRoIGdyb3VwZWQgY2FsY3VsYXRpb25zIG9mIG1lYW5zIG9mIHNsb3BlLCB3ZWlnaHQsIHRlbnNpb24sIGV4cGFuc2lvbiBhbmQgb3JpZW50YXRpb24gKGZyb20gYGBgZGZgYGApIHVzaW5nIGBgYHN0YXRzOjphZ2dyZWdhdGUoKWBgYC4gT25lIG9mIHRoZSB2YXJpYWJsZXMsIGBgYFNsb3BlYGBgLCBiYXNlZCBvbiBhIHNpbXBsZSBtZWFzdXJlbWVudCBvZiB0aGUgYW5nbGUgYmV0d2VlbiB0aGUgc3RlbSBvZiAiayIgYW5kIHRoZSBiYXNlIGxpbmUgKHNlZSBmaWcuIDcpLCByZWd1aXJlcyBubyBmdXJ0aGVyIHByb2Nlc3NpbmcgYW5kIGlzIGluY2x1ZGVkIGFzIGlzLgoKPGNlbnRlcj4KCiFbRmlnLiA3Ll0oZ2Z4LzgucG5nKXt3aWR0aD01MCV9Cgo8L2NlbnRlcj4KCmBgYHtyfQpzdW1tYXJ5LnR5cGVmYWNlIDwtIGFnZ3JlZ2F0ZShkZlsgLCBjKCJJbmRleCIsICJTbG9wZSIgLCAiV2VpZ2h0IiAsICJUZW5zaW9uIiAsICJFeHBhbnNpb24iICwgIlZlcnRPcmllbnRhdGlvbiIpXSwgYnk9bGlzdChkZiRUeXBlICwgZGYkVHlwZWZhY2UpLCBGVU49bWVhbikKc3VtbWFyeS50eXBlZmFjZSA8LSBhcy5kYXRhLmZyYW1lIChzdW1tYXJ5LnR5cGVmYWNlKQpgYGAKCioqMi42LiBDb25uZWN0aXZpdHkqKlwKVGhlIGNvbm5lY3Rpdml0eSBvZiBhIGZvbnQgZXhwcmVzc2VzIHRoZSBleHRlbnQgdG8gd2hpY2ggbGV0dGVycyBhcmUgY29ubmVjdGVkIHRvIG9uZSBhbm90aGVyLiBTY3JpcHQgYW5kIEhhbmR3cml0dGVuLCB3aGljaCBlbXVsYXRlcyBpdGFsaWMgd3JpdGluZywgdGVuZCB0byBoYXZlIGEgaGlnaCBkZWdyZWUgb2YgY29ubmVjdGVkbmVzcy4gU2FucyBTZXJpZiwgd2hpY2ggY29udGFpbnMgbm8gaW5mb3JtYXRpb24gYWJvdXQgaGFuZCB3cml0aW5nIGdlc3R1cmVzLCB0ZW5kIG5vdCBiZSBjb25uZWN0ZWQgYXQgYWxsLgoKPGNlbnRlcj4KCiFbRmlnLiA4XShnZngvOS5wbmcpe3dpZHRoPTUwJX0KPC9jZW50ZXI+CgpFeHBhbmQgYGBgc3VtbWFyeS50eXBlZmFjZWBgYCB3aXRoIHZhcmlhYmxlIGBgYENvbm5lY3Rpdml0eWBgYCB3aGljaCB3ZSdyZSBmZXRjaGluZyBmcm9tIHRoZSBgYGBzaGFwZV9kYXRhLmNzdmBgYCBmaWxlICh1bmVsZWdhbnQsIGJ1dCB3YXMgYW4gYWZ0ZXJ0aG91Z2h0KS4KCmBgYHtyfQpzdW1tYXJ5LnR5cGVmYWNlJENvbm5lY3Rpdml0eSA8LSBzaHBkZiRDb25uZWN0aXZpdHkgLyA1CmZlYXR1cmVzIDwtIGMoIlR5cGUiLCAiVHlwZWZhY2UiLCAiSW5kZXgiLCAiU2xvcGUiLCAiV2VpZ2h0IiwgIlRlbnNpb24iLCAiRXhwYW5zaW9uIiwgIlZlcnRPcmllbnRhdGlvbiIsICJDb25uZWN0aXZpdHkiKQpuYW1lcyhzdW1tYXJ5LnR5cGVmYWNlKSA8LSBmZWF0dXJlcwpybShmZWF0dXJlcykgIyBBIGJpdCBvZiBob3VzZWtlZXBpbmcKYGBgCgoqKjIuNy4gQ29udHJhc3QqKlwKQ29udHJhc3QgZXhwcmVzc2VzIHRoZSBkaWZmZXJlbmNlIGluIHdlaWdodCBvZiB0aGUgdGhpbm5lc3QgYW5kIHRoaWNrZXN0IHN0cm9rZSBpbiBhIGxldHRlci4KCjxjZW50ZXI+CgohW0ZpZy4gOV0oZ2Z4LzUucG5nKXt3aWR0aD01NSV9CjwvY2VudGVyPgoKQmVjYXVzZSBsZXR0ZXJzIGhhdmUgYSBkaWZmZXJlbnQgbnVtYmVyIG9mIHN0cm9rZXMgKCJIIiBoYXMgdGhyZWUsICJvIiBoYXMgb25lKSwgYXV0b21hdGljYWxseSBjYWxjdWxhdGluZyBgYGBDb250cmFzdGBgYCBiZXR3ZWVuIHN0cm9rZXMgaXMgYSBiaXQgbW9yZSB0cmlja3kgdGhhbiB0aGUgcHJldmlvdXMgZmVhdHVyZXMgKHdoaWNoIGVpdGhlciB0b29rIHRoZSBzdHJva2Ugb3IgdGhlIGVudGlyZSBsZXR0ZXIgYXMgdGhlaXIgdW5pdCBvZiBhbmFseXNpcykgYW5kIG5lZWRzIHNvbWUgZnVydGhlciBsaW5lcyBvZiBjb2RlOgoKVGhlIG5leHQgY2h1bmsgc2ltcGx5IGNhbGN1bGF0ZXMgaG93IG1hbnkgZm9udHMgYXJlIGN1cnJlbnRseSBpbiB0aGUgZGF0YXNldCAodW5kZXIgdGhlIGFzc3VtcHRpb24gdGhhdCBlYWNoIGZvbnQgaGFzIDEzIHJvd3MsIG9uZSBmb3IgZWFjaCBzdHJva2UpOgpgYGB7cn0Kb2JzZXJ2YXRpb25zIDwtIG5yb3coZGYpLzEzCmBgYAoKQmVjYXVzZSB0aGUgd2F5IHdlIGlkZW50aWZpZWQgc3Ryb2tlcyBpbiB0aGUgZGF0YXNldCAoY29uZmxhdGluZyBsZXR0ZXJzIGFuZCBzdHJva2VzIGluIGEgc2luZ2xlIGlkZW50aWZpZXIsIGUuZy4gSDEsIEgyKSBtYWtlcyBzdWJzZXR0aW5nIGRpZmZpY3VsdCwgdGhlIG5leHQgY2h1bmsgYWRkcyBhIHZhcmlhYmxlLCBgYGBMZXR0ZXJgYGAsIGFuZCByZXBlYXRzIGEgZmFjdG9yIHRvIGJ1aWxkIGEgbmV3IGlkZW50aWZpZXIgKFR1cm5zIG91dCBJIGNvdWxkIGhhdmUgZG9uZSB0aGlzIG1vcmUgZWFzaWx5IGluIE9wZW5SZWZpbmUpLgpgYGB7cn0KTGV0dGVyIDwtIGMocmVwKCJIIiwgMyksIHJlcCgicCIsIDIpLCByZXAoImsiLCAzKSwgcmVwKCJ4IiwgMiksIHJlcCgibyIsIDEpLCByZXAoImEiLCAyKSkgIyBBIGJpdCBvZiBkYXRhIHdyYW5nbGluZyBhZGRpbmcgYSB2YXJpYWJsZSwgbGV0dGVyLCBiZWNhdXNlIHRoZSB3YXkgd2UgZm9ybWF0ZWQgJ3N0cm9rZScgaW4gdGhlIGRhdGFzZXQgbWFrZXMgc3Vic2V0dGluZyBkaWZmaWN1bHQKZGYkTGV0dGVyIDwtIHJlcChMZXR0ZXIsIG9ic2VydmF0aW9ucykKcm0oTGV0dGVyLCBvYnNlcnZhdGlvbnMpICMgSG91c2VrZWVwaW5nCmBgYAoKVGhlIG5leHQgY2h1bmsgYWRkcyBhIHNjcmlwdCB0byBpZGVudGlmeSBzdHJva2VzIHdpdGggbWluaW11bSBhbmQgbWF4aW11bSB3ZWlnaHQgZm9yIGVhY2ggbGV0dGVyIGFuZCBjYWxjdWxhdGUgYSBjb250cmFzdCByYXRpbyAobWluIC8gbWF4KToKYGBge3J9CmNvbnRyYXN0LnJhdGlvIDwtIGZ1bmN0aW9uKEwpewogIGNob3NlbiA8LSBzdWJzZXQoZGYsIGRmJExldHRlcj09TCkKICBhIDwtIHNhcHBseShzcGxpdChjaG9zZW4kV2VpZ2h0LCBjaG9zZW4kVHlwZWZhY2UpLCBtaW4pCiAgYiA8LSBzYXBwbHkoc3BsaXQoY2hvc2VuJFdlaWdodCwgY2hvc2VuJFR5cGVmYWNlKSwgbWF4KQogIGMgPC0gYiAvIGEKICByZXR1cm4oYykKfQpgYGAKClRoZSBuZXh0IGNodW5rIGNyZWF0ZXMgYSBuZXcgaW50ZXJtZWRpYXRlIGRhdGFmcmFtZSwgYGBgZGYuY29udHJhc3RgYGAsIHdpdGggY29sdW1ucyBmb3IgY2FsY3VsYXRlZCBkaWZmZXJlbmNlcyBmb3IgZWFjaCBsZXR0ZXIgKGFuZCBhZGRpbmcgdGhlIGxldHRlciB5b3Ugd2FudCBpbiBwbGFjZSBvZiAnTCcpLgpgYGB7cn0KZGYuY29udHJhc3QgPC0gZGF0YS5mcmFtZShjb250cmFzdC5yYXRpbygiSCIpLCBjb250cmFzdC5yYXRpbygicCIpLCBjb250cmFzdC5yYXRpbygiayIpLCBjb250cmFzdC5yYXRpbygieCIpLCBjb250cmFzdC5yYXRpbygibyIpLCBjb250cmFzdC5yYXRpbygiYSIpKQpgYGAKCkNvbHVtbiBuYW1lcyBpbiBgYGBkZi5jb250cmFzdGBgYCBhcmUgaG9iYmlsZSwgc28gcmVuYW1lIHRoZW0gd2l0aCBmcmllbmRsaWVyIGxldHRlcnM6CmBgYHtyfQpjb2x1bW4uaGVhZGluZ3MgPC0gYygiSCIsICJwIiwgImsiLCAieCIsICJvIiwgImEiKQpuYW1lcyhkZi5jb250cmFzdCkgPC0gY29sdW1uLmhlYWRpbmdzCnJtKGNvbHVtbi5oZWFkaW5ncywgY29udHJhc3QucmF0aW8pCmBgYAoKQWRkIGEgY29sdW1uLCBgYGBtZWFuYGBgIHRvIGBgYGRmLmNvbnRyYXN0YGBgIGFuZCBmaWxsIHdpdGggY2FsY3VsYXRlZCBtZWFucyBmb3IgZWFjaCB0eXBlZmFjZToKYGBge3J9CmRmLmNvbnRyYXN0JG1lYW4gPC0gcm93U3VtcyhkZi5jb250cmFzdCkgLyBuY29sKGRmLmNvbnRyYXN0KQpgYGAKCkZldGNoIGBgYG1lYW5gYGAgZm9yIGVhY2ggdHlwZWZhY2UgZnJvbSBgYGBkZi5jb250cmFzdGBgYCBhbmQgYWRkIGFzIGNvbHVtbiBgYGBDb250cmFzdGBgYCB0byBgYGBzdW1tYXJ5LnR5cGVmYWNlYGBgOgpgYGB7cn0Kc3VtbWFyeS50eXBlZmFjZSRDb250cmFzdCA8LSBkZi5jb250cmFzdCRtZWFuCnJtKGRmLmNvbnRyYXN0KSAjIGhvdXNla2VlcGluZwpgYGAKCioqMi44LiBTaGFwZSoqXApUaGUgbmV4dCBsaW5lcyBkZXJpdmUgc2hhcGUgZGVzY3JpcHRvcnM7ICgxKSBEZW5zaXR5IHBlciByZWdpb24gKGBgYERyYGBgKSwgKDIpIFN0cmFpZ2h0IGFzIHByb3BvcnRpb24gb2YgRGVuc2l0eSAoYGBgU0RgYGApLCAoMykgQW5nbGUgYXMgcHJvcG9ydGlvbiBvZiBEZW5zaXR5IChgYGBBRGBgYCkgYW5kICg0KSBDdXJ2ZSBhcyBwcm9wb3J0aW9uIG9mIERlbnNpdHkgKGBgYENEYGBgKSwgYW5kIGFkZHMgdGhlbSB0byBzdW1tYXJ5LgoKYGBge3J9CnN1bW1hcnkudHlwZWZhY2UkRHIgPC0gKHNocGRmJFN0cmFpZ2h0ICsgc2hwZGYkQW5nbGUgKyBzaHBkZiRDdXJ2ZSkgLyBzaHBkZiRSZWdpb25zCmBgYApUaGlzIGFkZHMgYGBgRHJgYGAgdG8gdGhlIHN1bW1hcnkuCgpgYGB7cn0Kc3VtbWFyeS50eXBlZmFjZSRTRCA8LSBzaHBkZiRTdHJhaWdodCAvIChzaHBkZiRTdHJhaWdodCArIHNocGRmJEFuZ2xlICsgc2hwZGYkQ3VydmUpCmBgYApUaGlzIGFkZHMgYGBgU0RgYGAgdG8gdGhlIHN1bW1hcnkuCgpgYGB7cn0Kc3VtbWFyeS50eXBlZmFjZSRBRCA8LSBzaHBkZiRBbmdsZSAvIChzaHBkZiRTdHJhaWdodCArIHNocGRmJEFuZ2xlICsgc2hwZGYkQ3VydmUpCmBgYApUaGlzIGFkZHMgYGBgQURgYGAgdG8gdGhlIHN1bW1hcnkuCgpgYGB7cn0Kc3VtbWFyeS50eXBlZmFjZSRDRCA8LSBzaHBkZiRDdXJ2ZSAvIChzaHBkZiRTdHJhaWdodCArIHNocGRmJEFuZ2xlICsgc2hwZGYkQ3VydmUpCmBgYApUaGlzIGFkZHMgYGBgQ0RgYGAgdG8gdGhlIHN1bW1hcnkuCgojIDMuIFN1bW1hcnkgU3RhdGlzdGljc1wKQmVmb3JlIG1vdmluZyBvbiB0byBQcmluY2lwYWwgQ29tcMOlb25lbnQgQW5hbHlzaXMsIGhlcmUgYXJlIHN0YXRpc3RpY2FsIHN1bW1hcmllcyBmb3IgZWFjaCBvZiB0aGUgZml2ZSB0eXBvZ3JhcGhpY2FsIGNsYXNzZXMsIFNhbnMsIFNlcmlmLCBTbGFiIFNlcmlmLCBIYW5kd3JpdHRlbiBhbmQgU2NyaXB0LgoKKiozLjEuIFNhbnMgU2VyaWYgVHlwZWZhY2VzKipcCmBgYHtyfQpzdW1tYXJ5LnNhbnMgPC0gc3Vic2V0KHN1bW1hcnkudHlwZWZhY2UsIHN1bW1hcnkudHlwZWZhY2UkVHlwZSA9PSAiU2FucyIpCnN1bW1hcnkoc3VtbWFyeS5zYW5zKQpgYGAKCioqMy4yLiBTZXJpZiAoSHVtYW5pc3QpIFR5cGVmYWNlcyoqXApgYGB7cn0Kc3VtbWFyeS5zZXJpZiA8LSBzdWJzZXQoc3VtbWFyeS50eXBlZmFjZSwgc3VtbWFyeS50eXBlZmFjZSRUeXBlID09ICJTZXJpZiIpCnN1bW1hcnkoc3VtbWFyeS5zZXJpZikKYGBgCgoqKjMuMy4gU2xhYiBTZXJpZiBUeXBlZmFjZXMqKlwKYGBge3J9CnN1bW1hcnkuc2xhYiA8LSBzdWJzZXQoc3VtbWFyeS50eXBlZmFjZSwgc3VtbWFyeS50eXBlZmFjZSRUeXBlID09ICJTbGFiIikKc3VtbWFyeShzdW1tYXJ5LnNsYWIpCmBgYAoKKiozLjQuIEhhbmR3cml0dGVuIFR5cGVmYWNlcyoqXApgYGB7cn0Kc3VtbWFyeS5oYW5kIDwtIHN1YnNldChzdW1tYXJ5LnR5cGVmYWNlLCBzdW1tYXJ5LnR5cGVmYWNlJFR5cGUgPT0gIkhhbmQiKQpzdW1tYXJ5KHN1bW1hcnkuaGFuZCkKYGBgCgoqKjMuNS4gU2NyaXB0IFR5cGVmYWNlcyoqXApgYGB7cn0Kc3VtbWFyeS5zY3JpcHQgPC0gc3Vic2V0KHN1bW1hcnkudHlwZWZhY2UsIHN1bW1hcnkudHlwZWZhY2UkVHlwZSA9PSAiU2NyaXB0IikKc3VtbWFyeShzdW1tYXJ5LnNjcmlwdCkKYGBgCgojIDUuIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMKVXNpbmcgYGBgcHJjb21wKClgYGAgd2l0aCBub3JtYWxpemF0aW9uIChjb3JyZWxhdGlvbiBtYXRyaXgpIGJ5IHNldHRpbmcgYGBgc2NhbGUgPSBUUlVFYGBgIChGQUxTRSBnaXZlcyB1cyBhIGNvdmFyaWFuY2UgbWF0cml4IGluIHN0ZWFkKToKCmBgYHtyfQpyZXMucGNhIDwtIHByY29tcChzdW1tYXJ5LnR5cGVmYWNlWyAsIGMoNDoxNCldLCBzY2FsZSA9IFQpCmBgYAoKClRoZSBuZXh0IGNodW5rIG91dHB1dHMgYSBzY3JlZSBwbG90IHRvIGV4YW1pbmUgaG93IG1hbnkgUENzIHdlIG5lZWQgdG8gY29uc2lkZXIgYW5kIHdoYXQgaXMgbm9pc2U6CmBgYHtyfQpnZXRfZWlnZW52YWx1ZShyZXMucGNhKQpmdml6X2VpZyhyZXMucGNhKSAjIFNlZW1zIHRoZSBmaXJzdCAzIGFyZSBwcmV0dHkgcmVsZXZhbnQgKDc0LjcgJSB2YXJpYW5jZSBleHBsYWluZWQpLiBBZGRpbmcgdGhlIDR0aCBicmluZ3MgdXMgdG8gODglIHZhcmlhbmNlIGV4cGxhaW5lZC4KYGBgCkV4YW1pbmUgdGhlIHJlc3VsdHMgZm9yIFZhcmlhYmxlcyBvZiB0aGUgUENBCmBgYHtyfQpyZXMudmFyIDwtIGdldF9wY2FfdmFyKHJlcy5wY2EpCnJlcy52YXIkY29vcmQgICAgICAgICAgIyBDb29yZGluYXRlcwpyZXMudmFyJGNvbnRyaWIgIyBDb250cmlidXRpb25zIHRvIHRoZSBQQ3MKY29ycmVsYXRpb25fbWF0cml4IDwtIGFzLmRhdGEuZnJhbWUocmVzLnZhciRjb29yZCkgIyBTdG9yZSB0aGUgY29ycmVsYXRpb25zIGFzIGEgbWF0cml4IHdlIGNhbiBleGFtaW5lIGFuZCB1c2UgaW4gdGhlIGFydGljbGUKUENzIDwtIGMoIlBDMSIsICJQQzIiLCAiUEMzIiwgIlBDNCIsICJQQzUiLCAiUEM2IiwgIlBDNyIsICJQQzgiLCAiUEM5IiwgIlBDMTAiLCAiUEMxMSIpICMgUmVuYW1lIHRoZSB2YXJpYWJsZXMgZnJvbSBEaW0uMSB0byBQQzEsIHdoaWNoIGlzIG1vcmUgdHJhbnNwYXJlbnQKY29sbmFtZXMoY29ycmVsYXRpb25fbWF0cml4KSA8LSBQQ3MKcm0oUENzKSAjIEhvdXNla2VlcGluZwpgYGAKClBlcmZvcm0gS2Fpc2VyLU1leWVyLU9sa2luIHRlc3QgZm9yIG1lYXN1cmluZyBzYW1wbGluZyBhZGVxdWFjeS4gUmVzdWx0IGlzIG1pc2VyYWJsZSB3ZSdyZSBvbmx5IGdldHRpbmcgYSBNU0EgPSAwLjUKYGBge3J9CktNTyhjb3JyZWxhdGlvbl9tYXRyaXgpCmBgYAoKTWFrZSBmYW5jeSBiaXBsb3Qgb2YgUEMxIHZzLiBQQzIKYGBge3J9CnR5cGUgPC0gYXMuZmFjdG9yKHN1bW1hcnkudHlwZWZhY2UkVHlwZSkgIyBTdWJzZXRzIHR5cGVzIGZvciBjb2xvcmluZyB0aGUgcGxvdApmdml6X3BjYV9iaXBsb3QocmVzLnBjYSwKICAgICAgICAgICAgICAgIGF4ZXMgPSBjKDEsIDIpLAogICAgICAgICAgICAgICAgY29sLmluZCA9IHR5cGUsCiAgICAgICAgICAgICAgICBnZW9tID0gYygicG9pbnQiLCAidGV4dCIpLAogICAgICAgICAgICAgICAgbGFiZWwgPSAiYWxsIiwgbGFiZWxzaXplID0gMywgaW52aXNpYmxlID0gIm5vbmUiLAogICAgICAgICAgICAgICAgcmVwZWwgPSBGQUxTRSwKICAgICAgICAgICAgICAgIGhhYmlsbGFnZSA9ICJub25lIiwgCiAgICAgICAgICAgICAgICBwYWxldHRlID0gTlVMTCwKICAgICAgICAgICAgICAgIGFkZEVsbGlwc2VzID0gVFJVRSwgdGl0bGUgPSAiUENBIC0gQmlwbG90IgopCnJtKHR5cGUpICMgSG91c2VrZWVwaW5nCmBgYAoKYGBge3J9CnR5cGUgPC0gYXMuZmFjdG9yKHN1bW1hcnkudHlwZWZhY2UkVHlwZSkgIyBTdWJzZXRzIHR5cGVzIGZvciBjb2xvcmluZyB0aGUgcGxvdApmdml6X3BjYV9iaXBsb3QocmVzLnBjYSwKICAgICAgICAgICAgICAgIGF4ZXMgPSBjKDEsIDMpLAogICAgICAgICAgICAgICAgY29sLmluZCA9IHR5cGUsCiAgICAgICAgICAgICAgICBnZW9tID0gYygicG9pbnQiLCAidGV4dCIpLAogICAgICAgICAgICAgICAgbGFiZWwgPSAiYWxsIiwgbGFiZWxzaXplID0gMywgaW52aXNpYmxlID0gIm5vbmUiLAogICAgICAgICAgICAgICAgcmVwZWwgPSBGQUxTRSwKICAgICAgICAgICAgICAgIGhhYmlsbGFnZSA9ICJub25lIiwgCiAgICAgICAgICAgICAgICBwYWxldHRlID0gTlVMTCwKICAgICAgICAgICAgICAgIGFkZEVsbGlwc2VzID0gVFJVRSwgdGl0bGUgPSAiUENBIC0gQmlwbG90IgopCnJtKHR5cGUpICMgSG91c2VrZWVwaW5nCmBgYAoKTWFrZSBsZXNzIGZhbmN5IGJpcGxvdHMsIHRyeWluZyB0byByb3RhdGUgdGhlIGRhdGEgdG8gcmV2ZWFsIG90aGVyIHN0cnVjdHVyZXMgKHRoZXNlIGFyZSB0aGUgdHdvIG1vc3QgaW50ZXJlc3Rpbmcgb25lcykKYGBge3J9CmdnYmlwbG90KHJlcy5wY2EsIGNob2ljZXMgPSBjKDEsMiksIGdyb3VwcyA9IHN1bW1hcnkudHlwZWZhY2UkVHlwZSwgZWxsaXBzZSA9IFQsIGVsbGlwc2UucHJvYiA9IC45NSkKYGBgCgpgYGB7cn0KZ2diaXBsb3QocmVzLnBjYSwgY2hvaWNlcyA9IGMoMSwzKSwgZ3JvdXBzID0gc3VtbWFyeS50eXBlZmFjZSRUeXBlLCBlbGxpcHNlID0gVCwgZWxsaXBzZS5wcm9iID0gLjk1KQpgYGA=